package com.sys.utils;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.sql.*;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class DbUtil4 {

    private Connection conn = null;

    /**
     * 连接数据库，默认自动提交
     * @param driver
     * @param url
     * @param username
     * @param password
     * @throws ClassNotFoundException
     * @throws SQLException
     */
    public void connect(String driver,
                        String url,
                        String username,
                        String password) throws ClassNotFoundException, SQLException {

        connect(driver, url, username, password,true);
    }

    /**
     * 连接数据库，可以指定是否自动提交
     * @param driver
     * @param url
     * @param username
     * @param password
     * @param autoCommit
     * @throws ClassNotFoundException
     * @throws SQLException
     */
    public void connect(String driver,
                        String url,
                        String username,
                        String password,boolean autoCommit) throws ClassNotFoundException, SQLException {

        // 1。加载驱动，类比加载地图、读取地图
        try {
            Class.forName(driver);
            //2。连接数据库
            conn= DriverManager.getConnection(url,username,password);
            // 指定是否自动提交
            conn.setAutoCommit(autoCommit);
        } catch (ClassNotFoundException e) {
            throw e;
        } catch (SQLException e) {
            throw e;
        }
    }

    /**
     * insert into emp (empno,ename,job) values (?,?,?)
     * Object[]{1,'李雷','开发工程师'}
     * @param sql
     * @param params
     * @return
     * @throws ClassNotFoundException
     * @throws SQLException
     */
    public int update(
            String sql,
            Object[] params // 与 ? 相对应的参数，多个，参数可能是字符串、整型、浮点型，所以用的是Object
    ) throws SQLException {

        PreparedStatement prStat = null;
        try {
            //3。创建 Statement、PreparedStatement
            prStat = conn.prepareStatement(sql); // 预编译sql，所以创建该对象时就传入sql

            // 将sql中的 ? 与参数进行绑定
            if(params != null) {
                for (int i = 0; i < params.length; i++) {
                    // i + 1是因为从1开始
                    prStat.setObject(i + 1, params[i]);
                }
            }

            // 4. 执行增删改操作
            int i = prStat.executeUpdate();
            return i;
        } catch (SQLException e) {
            throw e;
        } finally {

            if(prStat!=null){
                try {
                    prStat.close();
                } catch (SQLException e) {
                    throw e;
                }
            }

        }
    }

    public void close() throws SQLException {
        if(conn!=null){
            try {
                conn.close();
            } catch (SQLException e) {
                throw e;
            }
        }
    }
    public void commit() throws SQLException {
        // 通过连接调用 提交事务，数据保存到数据库中
        conn.commit();
    }
    public void rollback() throws SQLException {
        // 通过连接调用回滚事务，让数据恢复到初始状态
        conn.rollback();
    }
    /*
        由于数据有行、列
            将每一行的所有列，封装到 linkedHashMap
            将所有的LinkedHashMap（表示多行），封装到 List

        String sql = "SELECT id,uname,upwd FROM t_user WHERE uname = ? AND upwd = ?";

     */


    /**
     *
     * @param sql 要执行的sql
     * @param params 与sql中的 ? 相对应的 参数
     * @param clazz 用来封装一行数据的类
     * @return 返回List，其元素是 clazz 类对象
     * @param <T>
     * @throws SQLException
     * @throws InvocationTargetException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws IntrospectionException
     */
    public <T> List<T> query(String sql,
                      Object[] params, // 与 ? 相对应的参数，多个，参数可能是字符串、整型、浮点型，所以用的是Object
                      Class clazz
    ) throws SQLException, InvocationTargetException, InstantiationException, IllegalAccessException, IntrospectionException {

        PreparedStatement prStat = null;
        ResultSet rs = null;
        try {
            //3。创建 Statement、PreparedStatement
            // 预编译sql，以后再次执行，效率高
            // 使用 ? 作为占位符，避免sql注入
            prStat = conn.prepareStatement(sql);

            // 将sql中的 ? 与参数进行绑定
            if(params != null) {
                for (int i = 0; i < params.length; i++) {
                    // i + 1是因为从1开始
                    prStat.setObject(i + 1, params[i]);
                }
            }
            //4。通过Statement 执行增删改查操作
            rs = prStat.executeQuery();

            // list泛型
            List reList = new ArrayList<>();
            while(rs.next()){ // 移动到下一行
                // 动态创建对象
                Object row = clazz.newInstance();
                BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
                PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
                for( PropertyDescriptor pd : propertyDescriptors){
                    // 获取成员变量，该成员变量一定有set方法或者get方法，或者同时都有
                    String field = pd.getName();
                    // 获取set方法
                    Method writeMethod = pd.getWriteMethod();
                    // 不为null表示有set方法
                    if(writeMethod!=null){
                        // 根据列名取出列值
                        Object value = null;
                        try {
                            value = rs.getObject(field);
                        }catch (SQLException e){
                            continue;
                        }
                        // mysql数据库的 decimal 类型，jdbc查询出来的类型是 BigDecimal，需要做判断
                        if ( value instanceof BigDecimal){
                            BigDecimal temp=(BigDecimal)value;
                            value=temp.doubleValue();
                        }

                        // 动态调用 setXXX 方法，并且传值
                        try {

                            writeMethod.invoke(row,value);
                        }catch (java.lang.IllegalArgumentException e){
                            throw new IllegalArgumentException(writeMethod.getName()+" 形参类型是："+writeMethod.getParameterTypes()[0]+
                                    "，从数据库查询出来的数据类型是："+value.getClass().getName());
                        }
                    }
                }

                reList.add(row);
            }
            return reList;
        } finally {

            if(rs!=null){
                rs.close();
            }
            if(prStat!=null){
                prStat.close();
            }
        }
    }

    public List<Map<String,Object>> query(
                                                 String sql,
                                                 Object[] params // 与 ? 相对应的参数，多个，参数可能是字符串、整型、浮点型，所以用的是Object
    ) throws SQLException {

        PreparedStatement prStat = null;
        ResultSet rs = null;
        try {
            //3。创建 Statement、PreparedStatement
            // 预编译sql，以后再次执行，效率高
            // 使用 ? 作为占位符，避免sql注入
            prStat = conn.prepareStatement(sql);

            // 将sql中的 ? 与参数进行绑定
            if(params != null) {
                for (int i = 0; i < params.length; i++) {
                    // i + 1是因为从1开始
                    prStat.setObject(i + 1, params[i]);
                }
            }
            //4。通过Statement 执行增删改查操作
            rs = prStat.executeQuery();

            // 元数据，描述信息，可以获取列数
            ResultSetMetaData metaData = rs.getMetaData();
            int columnCount = metaData.getColumnCount();

            // list泛型，表示里面的元素是 Map 集合，Map集合的key是字符串类型，value是Object类型
            List<   Map<String,Object>  > reList = new ArrayList<>();
            while(rs.next()){ // 移动到下一行
                // 将每一行的数据封装到map中
                Map<String ,Object> row = new LinkedHashMap<>();
                // 循环取出每一列的值，将每一列的值放入到Map中
                for( int i = 0;i<columnCount;i++){

                    String columnLabel = metaData.getColumnLabel(i + 1);
                    // 从1开始，所以是i+1
                    Object columnValue = rs.getObject(i+1);
                    // key是列名，value是值
                    row.put( columnLabel, columnValue);
                }
                // 将封装每一行的map，放入List中
                reList.add(row);

            }
            return reList;
        } catch (SQLException e) {
            throw e;
        } finally {

            if(rs!=null){
                try {
                    rs.close();
                } catch (SQLException e) {
                    throw e;
                }
            }
            if(prStat!=null){
                try {
                    prStat.close();
                } catch (SQLException e) {
                    throw e;
                }
            }
        }
    }


}
